use ftp::{types::FileType, FtpError, FtpStream};
use std::fs::File;
use std::io::{self, BufReader, Error, Read};
use std::path::Path;

const ERR_NOT_CONN: &str = "ftp not connect or not login!!!";

// fn contains_chinese(text:impl AsRef<str>) -> bool {
//     text.as_ref().chars().any(|c| ('\u{4E00}'..='\u{9FFF}').contains(&c))
// }
pub struct Ftp {
    host: String,
    port: u32,
    username: String,
    password: String,
    client: Option<FtpStream>,
}

impl Ftp {
    pub fn new(host: String, port: u32, username: String, password: String) -> Self {
        Self {
            host,
            port,
            username,
            password,
            client: None,
        }
    }

    pub fn connect(&mut self) -> Result<(), FtpError> {
        let ftp_stream = FtpStream::connect(format!("{}:{}", self.host, self.port))?;

        self.client.replace(ftp_stream);
        Ok(())
    }

    pub fn quit(&mut self) -> Result<(), FtpError> {
        if let Some(ftp_stream) = self.client.as_mut() {
            ftp_stream.quit()?;
            Ok(())
        } else {
            Err(FtpError::ConnectionError(Error::other(ERR_NOT_CONN)))
        }
    }

    pub fn login(&mut self) -> Result<(), FtpError> {
        if let Some(ftp_stream) = self.client.as_mut() {
            ftp_stream.login(&self.username, &self.password)?;
            // 显式启用 UTF-8 模式，否则中文目录和文件夹有问题
            // 发送自定义命令：启用 UTF-8
            let _response = ftp_stream.cmd("OPTS UTF8 ON")?;
            // println!("================{:?}", response);

            Ok(())
        } else {
            Err(FtpError::ConnectionError(Error::other(ERR_NOT_CONN)))
        }
    }

    pub fn cwd(&mut self, to_path: &String) -> Result<(), FtpError> {
        if let Some(ftp_stream) = self.client.as_mut() {
            let mut current_path = vec![""]; // PathBuf::new();

            let mut to_full_path_it = to_path.split("/").into_iter();
            while let Some(cpath) = to_full_path_it.next() {
                if cpath == "" {
                    continue;
                }
                //println!("cpath: {}", cpath);
                current_path.push(cpath);
                let current = current_path.join("/");
                //println!("current: {}", current);
                // if contains_chinese(current.as_str()) {
                //     use encoding_rs::GBK;
                //     let (gbk_bytes, _, _) = GBK.encode(current.as_str());
                //     current = String::from_utf8_lossy(&gbk_bytes).to_string();
                // }

                if let Err(e) = ftp_stream.cwd(current.as_str()) {
                    //println!("==cwd {} , error: {:?}", current, e);
                    match e {
                        FtpError::InvalidResponse(msg) => {
                            // InvalidResponse("Expected code [250], got response: 550 No such directory.\r\n")
                            // InvalidResponse("Expected code [250], got response: 550 End\r\n")
                            //println!("cwd {} , error: {}",current,msg);
                            //if msg.contains("550") {
                            // 创建子目录
                            ftp_stream.mkdir(current.as_str())?;
                            // 切换到子目录
                            ftp_stream.cwd(current.as_str())?;
                            //}
                        }
                        _ => (),
                    }
                }
            }
            Ok(())
        } else {
            Err(FtpError::ConnectionError(Error::other(ERR_NOT_CONN)))
        }
    }

    pub fn upload(
        &mut self,
        src_file_path: &Path,
        des_path: &String,
        des_filename: impl AsRef<str>,
        progress_callback: impl Fn(u8) + 'static,
    ) -> Result<(), FtpError> {
        let file = File::open(src_file_path).unwrap();
        let file_size = file.metadata().unwrap().len();
        let reader = BufReader::new(file);
        let mut progress_reader =
            ProgressReader::new(reader, file_size, Box::new(progress_callback));

        // 切换到目标目录
        self.cwd(des_path)?;

        if let Some(ftp_stream) = self.client.as_mut() {
            // Assuming ftp_stream is already defined and connected
            // 默认是文本模式 这里需要设置为2进制模式
            // <passive ports="30000-40000"  address="127.0.0.1"  external-address="127.0.0.1"/>
            ftp_stream.transfer_type(FileType::Binary)?;
            // let _response = ftp_stream.cmd("EPSV")?;
            ftp_stream.put(des_filename.as_ref(), &mut progress_reader)?;
        }
        Ok(())
    }
}

struct ProgressReader<R> {
    reader: R,
    total_size: u64,
    uploaded_size: u64,
    current_percent: u8,
    progress_callback: Box<dyn Fn(u8)>,
}

impl<R: Read> ProgressReader<R> {
    fn new(reader: R, total_size: u64, progress_callback: Box<dyn Fn(u8)>) -> Self {
        Self {
            reader,
            total_size,
            uploaded_size: 0,
            progress_callback,
            current_percent: 0,
        }
    }

    fn progress(&self) -> u8 {
        ((self.uploaded_size as f64 / self.total_size as f64) * 100.0) as u8
    }
}

impl<R: Read> Read for ProgressReader<R> {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        let bytes_read = self.reader.read(buf)?;
        self.uploaded_size += bytes_read as u64;

        // println!("Uploaded: {:.2}%", self.progress());
        let temp = self.progress();
        //只回调整数进度
        if temp > self.current_percent {
            // 测试延时
            // std::thread::sleep(std::time::Duration::from_secs(1));
            //tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;

            self.current_percent = temp;
            (self.progress_callback)(temp);
        }
        Ok(bytes_read)
    }
}
// https://course.rs/test/write-tests.html
#[cfg(test)]
mod tests {
    use super::*;
    //  只执行 ftp_upload 且打印println
    // cargo test ftp_upload  -- --nocapture
    #[test]
    fn ftp_upload() {
        let mut ftp = Ftp::new(
            "localhost".to_string(),
            2121,
            "xue".to_string(),
            "123456".to_string(),
        );
        ftp.connect().unwrap();
        ftp.login().unwrap();

        let r = ftp.upload(
            Path::new(
                "E:/BaiduNetdiskDownload/01.高数基础/01新文道版本/31.第五章-定积分的应用.mp4",
            ),
            &(String::from("/a/b/c/d")),
            "a.mp4",
            |percent| {
                println!("MY Uploaded: {:.2}%", percent);
            },
        );
        println!("--->{:?}", r);
        assert_eq!(r.is_ok(), true);
    }
}
